/**
 * $Id: mxEdgeStyle.java,v 1.29 2009/11/24 12:00:29 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.magnificent.graph.view;

import com.magnificent.graph.model.mxGeometry;
import com.magnificent.graph.model.mxIGraphModel;
import com.magnificent.graph.util.mxConstants;
import com.magnificent.graph.util.mxPoint;
import com.magnificent.graph.util.mxUtils;

import java.util.List;

/**
 * Provides various edge styles to be used as the values for
 * mxConstants.STYLE_EDGE in a cell style. Alternatevly, the mxConstants.
 * EDGESTYLE_* constants can be used to reference an edge style via the
 * mxStyleRegistry.
 */
public class mxEdgeStyle {

    /**
     * Defines the requirements for an edge style function.
     */
    public interface mxEdgeStyleFunction {

        /**
         * Implements an edge style function. At the time the function is called, the result
         * array contains a placeholder (null) for the first absolute point,
         * that is, the point where the edge and source terminal are connected.
         * The implementation of the style then adds all intermediate waypoints
         * except for the last point, that is, the connection point between the
         * edge and the target terminal. The first ant the last point in the
         * result array are then replaced with mxPoints that take into account
         * the terminal's perimeter and next point on the edge.
         *
         * @param state  Cell state that represents the edge to be updated.
         * @param source Cell state that represents the source terminal.
         * @param target Cell state that represents the target terminal.
         * @param points List of relative control points.
         * @param result Array of points that represent the actual points of the
         *               edge.
         */
        void apply(mxCellState state, mxCellState source, mxCellState target,
                   List<mxPoint> points, List<mxPoint> result);

    }

    /**
     * Provides an entity relation style for edges (as used in database
     * schema diagrams).
     */
    public static mxEdgeStyleFunction EntityRelation = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.magnificent.graph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, java.util.List, java.util.List)
           */

        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxIGraphModel model = view.getGraph().getModel();

            int segment = (int) (mxUtils.getDouble(state.getStyle(),
                    mxConstants.STYLE_STARTSIZE, mxConstants.ENTITY_SEGMENT) * state.view
                    .getScale());
            boolean isSourceLeft = false;

            if (source != null) {
                mxGeometry sourceGeometry = model.getGeometry(source.cell);

                if (sourceGeometry.isRelative()) {
                    isSourceLeft = sourceGeometry.getX() <= 0.5;
                } else if (target != null) {
                    isSourceLeft = target.getX() + target.getWidth() < source
                            .getX();
                }
            } else {
                mxPoint pt = state.absolutePoints.get(0);

                if (pt == null) {
                    return;
                }

                source = new mxCellState();
                source.setX(pt.getX());
                source.setY(pt.getY());
            }

            boolean isTargetLeft = true;

            if (target != null) {
                mxGeometry targetGeometry = model.getGeometry(target.cell);

                if (targetGeometry.isRelative()) {
                    isTargetLeft = targetGeometry.getX() <= 0.5;
                } else if (source != null) {
                    isTargetLeft = source.getX() + source.getWidth() < target
                            .getX();
                }
            } else {
                List<mxPoint> pts = state.absolutePoints;
                mxPoint pt = pts.get(pts.size() - 1);

                if (pt == null) {
                    return;
                }

                target = new mxCellState();
                target.setX(pt.getX());
                target.setY(pt.getY());
            }

            double x0 = (isSourceLeft) ? source.getX() : source.getX()
                    + source.getWidth();
            double y0 = view.getRoutingCenterY(source);

            double xe = (isTargetLeft) ? target.getX() : target.getX()
                    + target.getWidth();
            double ye = view.getRoutingCenterY(target);

            double seg = segment;

            double dx = (isSourceLeft) ? -seg : seg;
            mxPoint dep = new mxPoint(x0 + dx, y0);
            result.add(dep);

            dx = (isTargetLeft) ? -seg : seg;
            mxPoint arr = new mxPoint(xe + dx, ye);

            // Adds intermediate points if both go out on same side
            if (isSourceLeft == isTargetLeft) {
                double x = (isSourceLeft) ? Math.min(x0, xe) - segment : Math
                        .max(x0, xe)
                        + segment;
                result.add(new mxPoint(x, y0));
                result.add(new mxPoint(x, ye));
            } else if ((dep.getX() < arr.getX()) == isSourceLeft) {
                double midY = y0 + (ye - y0) / 2;
                result.add(new mxPoint(dep.getX(), midY));
                result.add(new mxPoint(arr.getX(), midY));
            }
            result.add(arr);
        }
    };

    /**
     * Provides a self-reference, aka. loop.
     */
    public static mxEdgeStyleFunction Loop = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.magnificent.graph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, java.util.List, java.util.List)
           */

        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxGraph graph = view.getGraph();
            mxPoint pt = (points != null && points.size() > 0) ? points.get(0)
                    : null;

            double s = view.getScale();

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);

                if (source.contains(pt.getX(), pt.getY())) {
                    pt = null;
                }
            }

            double x = 0;
            double dx = 0;
            double y = view.getRoutingCenterY(source);
            double dy = s * graph.getGridSize();

            if (pt == null || pt.getX() < source.getX()
                    || pt.getX() > source.getX() + source.getWidth()) {
                if (pt != null) {
                    x = pt.getX();
                    dy = Math.max(Math.abs(y - pt.getY()), dy);
                } else {
                    x = source.getX() + source.getWidth() + 2 * dy;
                }
            } else if (pt != null) {
                x = view.getRoutingCenterX(source);
                dx = Math.max(Math.abs(x - pt.getX()), dy);
                y = pt.getY();
                dy = 0;
            }

            result.add(new mxPoint(x - dx, y - dy));
            result.add(new mxPoint(x + dx, y + dy));
        }
    };

    /**
     * Uses either SideToSide or TopToBottom depending on the horizontal
     * flag in the cell style. SideToSide is used if horizontal is true or
     * unspecified.
     */
    public static mxEdgeStyleFunction ElbowConnector = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.magnificent.graph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, java.util.List, java.util.List)
           */

        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxPoint pt = (points != null && points.size() > 0) ? points.get(0)
                    : null;

            boolean vertical = false;
            boolean horizontal = false;

            if (source != null && target != null) {
                if (pt != null) {
                    double left = Math.min(source.getX(), target.getX());
                    double right = Math.max(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    double top = Math.min(source.getY(), target.getY());
                    double bottom = Math.max(
                            source.getY() + source.getHeight(), target.getY()
                                    + target.getHeight());

                    mxGraphView view = state.getView();
                    pt = view.transformControlPoint(state, pt);

                    vertical = pt.getY() < top || pt.getY() > bottom;
                    horizontal = pt.getX() < left || pt.getX() > right;
                } else {
                    double left = Math.max(source.getX(), target.getX());
                    double right = Math.min(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    vertical = left == right;

                    if (!vertical) {
                        double top = Math.max(source.getY(), target.getY());
                        double bottom = Math.min(source.getY()
                                + source.getHeight(), target.getY()
                                + target.getHeight());

                        horizontal = top == bottom;
                    }
                }
            }

            if (!horizontal
                    && (vertical || mxUtils.getString(state.getStyle(),
                    mxConstants.STYLE_ELBOW, "").equals(
                    mxConstants.ELBOW_VERTICAL))) {
                mxEdgeStyle.TopToBottom.apply(state, source, target, points,
                        result);
            } else {
                mxEdgeStyle.SideToSide.apply(state, source, target, points,
                        result);
            }
        }
    };

    /**
     * Provides a vertical elbow edge.
     */
    public static mxEdgeStyleFunction SideToSide = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.magnificent.graph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, java.util.List, java.util.List)
           */

        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxPoint pt = ((points != null && points.size() > 0) ? points.get(0)
                    : null);

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);
            }

            if (source == null) {
                mxPoint tmp = state.absolutePoints.get(0);

                if (tmp == null) {
                    return;
                }

                source = new mxCellState();
                source.setX(tmp.getX());
                source.setY(tmp.getY());
            }

            if (target == null) {
                List<mxPoint> pts = state.absolutePoints;
                mxPoint tmp = pts.get(pts.size() - 1);

                if (tmp == null) {
                    return;
                }

                target = new mxCellState();
                target.setX(tmp.getX());
                target.setY(tmp.getY());
            }

            double l = Math.max(source.getX(), target.getX());
            double r = Math.min(source.getX() + source.getWidth(), target
                    .getX()
                    + target.getWidth());

            double x = (pt != null) ? pt.getX() : r + (l - r) / 2;

            double y1 = view.getRoutingCenterY(source);
            double y2 = view.getRoutingCenterY(target);

            if (pt != null) {
                if (pt.getY() >= source.getY()
                        && pt.getY() <= source.getY() + source.getHeight()) {
                    y1 = pt.getY();
                }

                if (pt.getY() >= target.getY()
                        && pt.getY() <= target.getY() + target.getHeight()) {
                    y2 = pt.getY();
                }
            }

            if (!target.contains(x, y1) && !source.contains(x, y1)) {
                result.add(new mxPoint(x, y1)); // routed
            }

            if (!target.contains(x, y2) && !source.contains(x, y2)) {
                result.add(new mxPoint(x, y2)); // routed
            }

            if (result.size() == 1) {
                if (pt != null) {
                    result.add(new mxPoint(x, pt.getY())); // routed
                } else {
                    double t = Math.max(source.getY(), target.getY());
                    double b = Math.min(source.getY() + source.getHeight(),
                            target.getY() + target.getHeight());

                    result.add(new mxPoint(x, t + (b - t) / 2));
                }
            }
        }

    };

    /**
     * Provides a horizontal elbow edge.
     */
    public static mxEdgeStyleFunction TopToBottom = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.magnificent.graph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, com.magnificent.graph.view.mxCellState, java.util.List, java.util.List)
           */

        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxPoint pt = ((points != null && points.size() > 0) ? points.get(0)
                    : null);

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);
            }

            if (source == null) {
                mxPoint tmp = state.absolutePoints.get(0);

                if (tmp == null) {
                    return;
                }

                source = new mxCellState();
                source.setX(tmp.getX());
                source.setY(tmp.getY());
            }

            if (target == null) {
                List<mxPoint> pts = state.absolutePoints;
                mxPoint tmp = pts.get(pts.size() - 1);

                if (tmp == null) {
                    return;
                }

                target = new mxCellState();
                target.setX(tmp.getX());
                target.setY(tmp.getY());
            }

            double t = Math.max(source.getY(), target.getY());
            double b = Math.min(source.getY() + source.getHeight(), target
                    .getY()
                    + target.getHeight());

            double x = view.getRoutingCenterX(source);

            if (pt != null && pt.getX() >= source.getX()
                    && pt.getX() <= source.getX() + source.getWidth()) {
                x = pt.getX();
            }

            double y = (pt != null) ? pt.getY() : b + (t - b) / 2;

            if (!target.contains(x, y) && !source.contains(x, y)) {
                result.add(new mxPoint(x, y)); // routed
            }

            if (pt != null && pt.getX() >= target.getX()
                    && pt.getX() <= target.getX() + target.getWidth()) {
                x = pt.getX();
            } else {
                x = view.getRoutingCenterX(target);
            }

            if (!target.contains(x, y) && !source.contains(x, y)) {
                result.add(new mxPoint(x, y)); // routed
            }

            if (result.size() == 1) {
                if (pt != null) {
                    result.add(new mxPoint(pt.getX(), y)); // routed
                } else {
                    double l = Math.max(source.getX(), target.getX());
                    double r = Math.min(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    result.add(new mxPoint(l + (r - l) / 2, y));
                }
            }
        }
    };

}

  /* converted to utf8 */